#include "pch.h"
#include "GameObject.h"
#include "Ship.h"
#include "SoundManager.h"
#include <algorithm>

dxroids::Ship::Ship(Vector const & _pos, float _scale, float _bounding_radius, Attributes const & _attrs)
   : GameObject(_pos, _bounding_radius, _scale)   
   , thrust_enabled_(false) 
   , attrs_(_attrs)
{   
}

DXROIDS_COM_PTR<ID2D1Geometry> dxroids::Ship::CreateGeometry(
   DXROIDS_COM_PTR<ID2D1Factory> const & _factory, float _scale)
{
   for (int i = 0; i < attrs_.MaximumNumberOfShots; ++i)
   {    
      std::unique_ptr<Photon> photon(new Photon(GetPosition(), Vector(DXROIDS_PLAYER_SHIP_PHOTON_VELOCITY, 0.0f), 
         attrs_.MaximumShotDistance));
      photon->Initialize(_factory);
      photon->SetIsActive(false);      
      shots_ready_to_fire_.push_back(std::move(photon));
   }

   thrust_gemoetry_ = CreateThrustGeometry(_factory, _scale);
   return CreateShipGeometry(_factory, _scale);
}

bool dxroids::Ship::IsShotHittingObject(GameObject const & _rhs) const
{
   for (std::unique_ptr<Photon> const & shot : shots_fired_)
   {
      if (shot == nullptr)
      {
         continue;
      }
      if (shot->IntersectsWith(_rhs))
      {
         shot->SetIsActive(false);
         return true;
      }
   }
   return false;
}

float dxroids::Ship::GetNextShotDirection() const
{
   return GetDirection();
}

dxroids::Vector dxroids::Ship::GetNextShotPosition() const
{
   return GetPosition();
}

bool dxroids::Ship::FireShot(std::shared_ptr<SoundManager> const & _sound_manager)
{
   if (!fire_shot_enabled_ || !IsActive())
   {
      return false;
   }

   auto available_shot = std::find_if(shots_ready_to_fire_.begin(), shots_ready_to_fire_.end(), 
      [&](std::unique_ptr<Photon> & shot) { return shot != nullptr; });

   if (available_shot == shots_ready_to_fire_.end())
   {
      return false;
   }
   
   std::unique_ptr<Photon> photon = std::move(*available_shot);   
   photon->SetIsActive(true);

   Vector velocity(0.0f, -attrs_.ShotVelocity);
   velocity.Rotate(DegreesToRadians(GetNextShotDirection()));   
   
   photon->SetPosition(GetNextShotPosition());
   photon->SetVelocity(velocity);

   // position should be at the 'tip' of the ship, right now we're firing from the center.
   
   auto slot = std::find_if(shots_fired_.begin(), shots_fired_.end(), 
      [&](std::unique_ptr<Photon> & shot) { return shot == nullptr; });

   if (slot == shots_fired_.end())
   {
      shots_fired_.push_back(std::move(photon));
   }
   else
   {
      *slot = std::move(photon);
   }

   if (_sound_manager)
   {
      _sound_manager->Play(SoundEffect::Fire, false);
   }
   
   return true;
}

void dxroids::Ship::Render(RenderContext const & _ctx)
{   
   GameObject::Render(_ctx);

   if (thrust_enabled_)
   {
      DxRoidsAssert(thrust_gemoetry_ != nullptr);
      _ctx.Target->DrawGeometry(thrust_gemoetry_, _ctx.PrimaryBrush); 
   }

   for (std::unique_ptr<Photon> & shot : shots_fired_)      
   {
      if (shot != nullptr)
      {
         shot->Render(_ctx);      
      }
   }
}

void dxroids::Ship::UpdateFrame(
   GameTime const & _timer, 
   ClippingInformation const & _clip, 
   bool * _wrap_horizontal, 
   bool * _wrap_vertical)
{
   if (thrust_enabled_)
   {      
      float const x = sinf(DegreesToRadians(GetDirection())) * attrs_.ThrustMagnitude;
      float const y = cosf(DegreesToRadians(GetDirection())) * attrs_.ThrustMagnitude;

      float const vx = velocity_.GetX();
      if (vx == 0.0f || fabs(vx) < Vector::MinTolerance)
      {
         velocity_.SetX(x);
      }
      else
      {
         velocity_.SetX(vx + x);         
      }

      float const vy = velocity_.GetY();
      if (vy == 0.0f || fabs(vy) < Vector::MinTolerance)
      {               
         velocity_.SetY(-y);
      }
      else
      {
         velocity_.SetY(vy + -y);         
      }

      float const max_velocity_squared = (attrs_.MaximumVelocity * attrs_.MaximumVelocity);
      if (velocity_.MagnitudeSquared() > max_velocity_squared)
      {         
         velocity_ *= (attrs_.MaximumVelocity / velocity_.Magnitude());
      }
   }

   GameObject::UpdateFrame(_timer, _clip, _wrap_horizontal, _wrap_vertical);

   for (std::unique_ptr<Photon> & shot : shots_fired_)   
   {      
      if (shot != nullptr)
      {
         shot->UpdateFrame(_timer, _clip, nullptr, nullptr);
         if (!shot->IsActive())
         {
            shots_ready_to_fire_.push_back(std::move(shot));            
         }
      }
   }
}

dxroids::Ship::Photon::Photon(Vector const & _pos, Vector _velocity, float _max_distance)
   : GameObject(_pos, 5.0f, 1.0f)   
   , max_distance_(_max_distance) 
   , num_frames_(0)
   , max_frames_(static_cast<int>(ceilf(_max_distance / _velocity.Magnitude())))
{   
}

DXROIDS_COM_PTR<ID2D1Geometry> dxroids::Ship::Photon::CreateGeometry(
   DXROIDS_COM_PTR<ID2D1Factory> const & _factory, float _scale)
{   
   DXROIDS_COM_PTR<ID2D1EllipseGeometry> ellipse_geometry;
   D2D1_ELLIPSE ellipse = D2D1::Ellipse(D2D1::Point2F(), 1.0f, 1.0f);
   ThrowIfFailed(_factory->CreateEllipseGeometry(&ellipse, &ellipse_geometry));
   DXROIDS_COM_PTR<ID2D1Geometry> geometry(ellipse_geometry);
   return geometry;
}

bool dxroids::Ship::Photon::IntersectsWithGeometry(DXROIDS_COM_PTR<ID2D1Geometry> const & _rhs) const
{       
   D2D1_POINT_2F pos;
   pos.x = GetPosition().GetX();
   pos.y = GetPosition().GetY();
   
   BOOL contains = FALSE;
   ThrowIfFailed(_rhs->FillContainsPoint(pos, nullptr, &contains));    
   return contains == TRUE ? true : false;
}


void dxroids::Ship::Photon::UpdateFrame(
   GameTime const & _timer, 
   ClippingInformation const & _clip, 
   bool * _wrap_horizontal, 
   bool * _wrap_vertical)
{
   GameObject::UpdateFrame(_timer, _clip, _wrap_horizontal, _wrap_vertical);

   if (num_frames_++ >= max_frames_)
   {   
      SetIsActive(false);
      num_frames_ = 0;
   }
}

void dxroids::Ship::EnableThrust(std::shared_ptr<SoundManager> & _sound_manager)
{
   if (IsActive() && !thrust_enabled_)
   {
      thrust_enabled_ = true;
      _sound_manager->Play(SoundEffect::Thrust, true);         
   }
}


void dxroids::Ship::DisableThrust(std::shared_ptr<SoundManager> & _sound_manager)
{
   if (thrust_enabled_)
   {
      thrust_enabled_ = false;
      _sound_manager->Stop(SoundEffect::Thrust);
   }
}
